Описание мультиплексоров в SystemVerilog
Мультипле́ксор — устройство, имеющее несколько сигнальных входов, один или более управляющих входов и один выход. Мультиплексор позволяет передавать сигнал с одного из входов на выход; при этом выбор желаемого входа осуществляется подачей соответствующей комбинации управляющих сигналов[1].
Иными словами, мультиплексор — это переключатель (коммутатор), соединяющий выход с одним из множества входов.
Для начала создадим простой двухвходовой мультиплексор. Предположим, на Y
нам необходимо передать один из сигналов — D0
или D1
в зависимости от значения управляющего сигнала S
: когда S==0
, на Y
подается сигнал D0
, в противном случае — D1
.
На языке SystemVerilog это можно описать несколькими способами. Первый — с помощью тернарного условного оператора:
Тернарный условный оператор
О тернарном условном операторе
Операторы бывают различной арности(количества аргументов оператора[операндов]):
- унарный (с одним операндом), пример:
-a
; - бинарный (с двумя операндами), пример:
a+b
; - тернарный (с тремя операндами), пример:
cond ? if_true : false
; - и др.
Несмотря на то, что тернарным оператором может быть любой оператор, принимающий три операнда, обычно под ним подразумевается тернарный условный оператор, работающий следующим образом:
<условие> ? <значение_если_условие_истинно> : <значение_если_условие_ложно>
Первым операндом идет некоторое условие (любое выражение, которое может быть сведено к 1 или 0). Далее ставится знак вопроса (часть тернарного оператора, отделяющая выражение первого операнда от выражения второго операнда). Далее пишется выражение, которое будет результатом тернарного условного оператора в случае, если условие оказалось истинным. После чего ставится двоеточие (часть тернарного условного оператора, отделяющая выражение второго операнда от выражения третьего операнда). Затем пишется выражение, которое будет результатом тернарного условного оператора в случае, если условие оказалось ложным.
Пример для языка C++:
a = b+c >= 5 ? b+c : b+d;
Сперва вычисляется первый операнд (выражение b+c >= 5
). Если это выражение оказалось истинным (равно единице), то переменной a
будет присвоено значение второго операнда (выражения b+c
), в противном случае переменной a
будет присвоено значение третьего операнда (выражения b+d
).
logic Y;
assign Y = S==1 ? D1 : D0;
Данное выражение говорит нам, что если S==1
, то Y
присваивается значение D1
, в противном случае — значение D0
.
Также мультиплексор можно описать через конструкцию if-else
в блоке always
.
Далее будет ключевой параграф сложного для понимания текста, очень важно запомнить, что там написано и разобрать приведенные листинги.
Блок always
Блок always
— это специальный блок, который позволяет описывать комбинационные и последовательностные схемы (см. документ "Последовательностная логика"), используя более сложные конструкции, такие как if-else
, case
. На самом деле, в языке SystemVerilog помимо общего блока always
, которым можно описать любой вид логики, существует множество специализированных блоков, предназначенных для описания отдельно комбинационной, синхронной и последовательностной асинхронной логики соответственно:
- always_comb
- always_ff
- always_latch
Мультиплексор можно описать в любом из этих блоков, разница будет лишь в том, к чему именно будет подключен выход мультиплексора: к проводу, регистру, или защелке.
В зависимости от вида always
-блока используется один из двух видов присваиваний: блокирующее присваивание (=
) и неблокирующего присваивания (<=
). Подробно о различиях между присваиваниями рассказано в этом документе. До его прочтения запомните:
- внутри блока
always_ff
иalways_latch
необходимо использовать оператор неблокирующего присваивания (<=
); - внутри блока
always_comb
необходимо использовать оператор блокирующего присваивания (=
).
Остановитесь на выделенном выше фрагменте документа, пока полностью не разберете его. Без освоения всех описанных выше особенностей языка SystemVerilog вы столкнетесь в будущем с множеством ошибок.
Блок if-else
Реализация мультиплексора через блок if-else
похожа на реализацию мультиплексора через тернарный оператор. Если в тернарном операторе управляющий сигнал указывался в качестве первого операнда, отделяемого оператором ?
, то в данном блоке управляющий сигнал указывается в скобках после ключевого слова if
.
Далее описывается присваивание сигнала, который должен идти на выход при управляющем сигнале равном единице (значение до оператора :
в тернарном операторе).
После, в блоке else
описывается присваивание сигнала, который должен идти на выход при управляющем сигнале равном нулю (значение после оператора :
в тернарном операторе).
logic Y;
always_comb begin // 1) Используется always_comb, т.к. мы хотим подключить
// выход мультиплексора к проводу
if(S) begin // 2) if-else может находиться только внутри блока always.
Y = D1; // 3) Используется оператор блокирующего присваивания.
end else begin
Y = D0;
end
end
Кроме того, важно запомнить, что присваивание сигналу допускается только в одном блоке always.
Неправильно:
logic Y;
always_comb begin
if(S==1) begin
Y = D1;
end
end
always_comb begin
if(S==0) begin // Нельзя выполнять операцию присваивания
Y = D0; // для одного сигнала (Y) в нескольких
end // блоках always!
end
Если нарушить это правило, то в будущем (возможно не сразу, но в любом случае — обязательно), возникнет ошибка, которая так или иначе будет связана с multiple drivers.
Будьте очень внимательны при использовании данного блока. Он обманчиво похож на условный блок в языках программирования, из-за чего возникает желание пользоваться им так же, как можно пользоваться условными блоками в языках программирования. Это не так. Обратите внимание на то, что данный блок выше упоминается исключительно как блок if-else
. При реализации мультиплексора, у любого блока if
должен быть соответствующий блок else
, как у тернарного оператора должно быть два выходных операнда. Если не указать блок else
при описании мультиплексора, у него будет только один вход, и в итоге на выходе мультиплексора будет сгенерирована защелка. Подробнее о защелках описано здесь.
Существуют ситуации, когда блок if
может быть использован без блока else
(например, при описании дешифраторов или сигналов разрешения записи). Однако при описании мультиплексоров таких ситуаций не бывает.
case-блок
Мультиплексор также можно описать с использованием конструкции case. Блок case
лучше подходит для описания мультиплексора, когда у того более двух входов (ведь в случае конструкции if-else
пришлось бы делать вложенное ветвление).
Конструкция case
представляет собой инструмент множественного ветвления, который сравнивает значение заданного выражения с множеством вариантов, и, в случае первого совпадения, использует соответствующую ветвь. На случай, если ни один из вариантов не совпадет с заданным выражением, конструкция case
поддерживает вариант default
. Данная конструкция визуально похожа на оператор switch-case
в Си, однако вы должны понимать, что используется она не для написания программы, а описания аппаратуры, в частности мультиплексоров/демультиплексоров и дешифраторов.
Конструкция case
, наряду с if-else
, может быть описана только в блоке always
.
Реализация двухвходового мультиплексора с помощью case
может выглядеть так:
logic Y;
always_comb begin
case(S) // Описываем блок case, где значение сигнала S
// будет сравниваться с различными возможными его значениями
1'b0: Y = D0; // Если S==0, то Y = D0
1'b1: Y = D1;
endcase // Каждый case должен заканчиваться endcase
end // (так же как каждый begin должен оканчиваться end)
Рассмотрим вариант посложнее и опишем следующую схему:
Здесь уже используется мультиплексор 8в1. Управляющий сигнал S
в данном случае трёхбитный. В блоке case
мы перечисляем всевозможные варианты значений S
и описываем выход мультиплексора.
module case_mux_ex(
input logic A,
input logic B,
input logic C,
input logic D,
input logic [2:0] S,
output logic Y
);
always_comb begin
case(S)
3'b000: Y = A;
3'b001: Y = C | B; // в блоке case можно мультиплексировать
// не только провода, но и логические выражения
3'b010: Y = (C|B) & D;
/*
Обратите внимание, что разрядность сигнала S — 3 бита.
Это означает, что есть 8 комбинаций его разрядов.
Выше было описано только 3 комбинации из 8.
Если для всех остальных комбинаций на выходе мультиплексора должно
быть какое-то одно значение "по умолчанию", используется специальная
комбинация "default":
*/
default: Y = D;
endcase
end
endmodule
Оператор адресации
Представим, что нам необходимо мультиплексировать реально много сигналов. Например, отдельные биты 1024-разрядной шины. Описывать case
на 1024 варианта будет сущим безумием. В этом случае, можно будет воспользоваться оператором '[]', который наверняка известен вам как "оператор адресации по массиву" в Си-подобных языках. Работает он интуитивно понятно:
- перед оператором указывается имя массива или вектора (читай как "памяти или шины"), по которым будет идти индексация;
- за именем в квадратных скобках указывается индекс (не важно, в виде константы, или выражения, использующего другие сигналы).
В контексте примера по мультиплексированию 1024 бит использование оператора может быть выполнено следующим образом:
logic [1023:0] bus1024;
logic [ 9:0] select;
logic one_bit_result;
assign one_bit_result = bus1024[select];
Реализация мультиплексоров через оператор '[]' будет активно применяться вами при реализации различных памятей.
Итоги главы
- Мультиплексор — это комбинационный блок, подающий на выход один из нескольких входных сигналов.
- Мультиплексор можно описать множеством способов, среди них:
- использование тернарного условного оператора;
- использование конструкции
if-else
внутри блокаalways
; - использование конструкции
case
внутри блокаalways
; - использование оператора '[]'.
- Во избежание появления защелок при описании мультиплексора, необходимо убедиться что у блоков
if
есть соответствующие им блокиelse
, а у мультиплексоров описаны все комбинации управляющего сигнала (при необходимости, множество оставшихся комбинаций можно покрыть с помощью комбинацииdefault
). Появление непреднамеренной защелки в дизайне ведет к ухудшению временных характеристик, избыточному использованию ресурсов, а также непредсказуемому поведению схемы из-за возможного удержания сигнала. - Важно отметить, что блоки
if-else
иcase
могут использоваться не только для описания мультиплексоров. - Конструкции
if-else
иcase
в рамках данных лабораторных работ можно описывать только внутри блокаalways
. При работе с этим блоком необходимо помнить следующие особенности:- Существует несколько типов блока
always
:always_comb
,always_ff
,always_latch
, определяющих то, к чему будет подключена описанная в этом блоке логика: проводу, регистру или защелке соответственно. В данных лабораторных работах вам нужно будет пользоваться блокамиalways_ff
иalways_comb
, причем:- внутри блока
always_ff
необходимо использовать оператор неблокирующего присваивания (<=
); - внутри блока
always_comb
необходимо использовать оператор блокирующего присваивания (=
).
- внутри блока
- Присваивание для любого сигнала возможно только внутри одного блока always. Два разных сигнала могут присваиваться как в одном блоке always, так и каждый в отдельном, но операция присваивания одному и тому же сигналу в двух разных блоках always — нет.
- Существует несколько типов блока
Проверь себя
Как описать на языке SystemVerilog следующую схему?